/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */
using System;
using System.Data;
using System.Collections.Generic;
using System.IO;

namespace TopCoder.Util.Calendar.Entry.Impl
{
    /// <summary>
    /// <para>This class is a simple implementation of the <c>ICalendarEntryDataSource</c> interface.</para>
    /// <para>To make it easy to work, this class does not use any database, it just loads a specified XML document
    /// and the specified schema from files.</para>
    /// </summary>
    /// <remarks>Thread Safety: This class is thread safe.</remarks>
    /// <author>Ghostar</author>
    /// <author>yext</author>
    /// <author>argolite</author>
    /// <author>alex.dolin</author>
    /// <version>1.1</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public class XmlCalendarEntryDataSource : ICalendarEntryDataSource
    {
        /// <summary>
        /// <para>This private dictionary stores all the calendar entries as the values, and the entry IDs as
        /// the keys.</para>
        /// </summary>
        private readonly IDictionary<int, CalendarEntry> calendarEntries
            = new Dictionary<int, CalendarEntry>();

        /// <summary>
        /// <para>This private dictionary stores all the calendar entry types as the values, and the type IDs as
        /// the keys.</para>
        /// </summary>
        private readonly IDictionary<int, CalendarEntryType> calendarEntryTypes
            = new Dictionary<int, CalendarEntryType>();

        /// <summary>
        /// <para>Represents the name of the table which stores data for calendar entries.</para>
        /// </summary>
        public const string CalendarEntryTableName = "CalendarEntry";

        /// <summary>
        /// <para>Represents the name of the table which stores data for calendar entry types.</para>
        /// </summary>
        public const string CalendarEntryTypeTableName = "CalendarEntryType";

        /// <summary>
        /// <para>The constructor of this class.</para>
        /// </summary>
        /// <param name="schemaFileName">The name of the schema file.</param>
        /// <param name="dataFileName">The name of the file containing data.</param>
        /// <exception cref="CalendarEntryDataSourceException">If any errors occurred when retrieving the data.
        /// </exception>
        public XmlCalendarEntryDataSource(string schemaFileName, string dataFileName)
        {
            DataSet dataSet = new DataSet();
            try
            {
                dataSet.ReadXmlSchema(schemaFileName);
                dataSet.ReadXml(dataFileName);
            }
            catch (Exception e)
            {
                throw new CalendarEntryDataSourceException("Error while reading data.", e);
            }

            InitDataSource(dataSet);
        }

        /// <summary>
        /// <para>The constructor of this class.</para>
        /// </summary>
        /// <param name="schemaStream">The stream to read the schema from.</param>
        /// <param name="dataStream">The stream to read data from.</param>
        /// <exception cref="CalendarEntryDataSourceException">If any errors occurred when retrieving the data.
        /// </exception>
        public XmlCalendarEntryDataSource(Stream schemaStream, Stream dataStream)
        {
            DataSet dataSet = new DataSet();
            try
            {
                dataSet.ReadXmlSchema(schemaStream);
                dataSet.ReadXml(dataStream);
            }
            catch (Exception e)
            {
                throw new CalendarEntryDataSourceException("Error while reading data.", e);
            }

            InitDataSource(dataSet);
        }

        /// <summary>
        /// Initializes instance of data source from the given data set.
        /// </summary>
        /// <param name="dataSet">Data set which contains data for this data source.</param>
        /// <exception cref="CalendarEntryDataSourceException">If any errors occurred when retrieving the data.
        /// </exception>
        private void InitDataSource(DataSet dataSet)
        {
            DataTable calendarEntryTable = dataSet.Tables[CalendarEntryTableName];
            DataTable calendarEntryTypeTable = dataSet.Tables[CalendarEntryTypeTableName];

            foreach (DataRow row in calendarEntryTypeTable.Rows)
            {
                int typeId;
                try
                {
                    typeId = (int)row["TypeId"];
                }
                catch (InvalidCastException e)
                {
                    throw new CalendarEntryDataSourceException(
                        "TypeId field should be a non-negative integer.", e);
                }
                if (typeId < 0)
                {
                    throw new CalendarEntryDataSourceException(
                        "TypeId field should be non-negative.");
                }
                if (calendarEntryTypes.ContainsKey(typeId))
                {
                    throw new CalendarEntryDataSourceException(
                        "The types shouldn't have duplicate IDs.");
                }

                calendarEntryTypes[typeId] = new CalendarEntryType(typeId, (string)row["Description"]);
            }

            foreach (DataRow row in calendarEntryTable.Rows)
            {
                CalendarEntry entry = new CalendarEntry();
                entry.Date = (DateTime)row["Date"];
                entry.Description = (string)row["Description"];
                entry.EntryId = (int)row["EntryId"];

                if (calendarEntries.ContainsKey(entry.EntryId))
                {
                    throw new CalendarEntryDataSourceException(
                        "The entries shouldn't have duplicate IDs.");
                }

                try
                {
                    entry.Priority = (Priority)row["Priority"];
                }
                catch
                {
                    entry.Priority = Priority.Medium;
                }

                entry.Type = FindEntryTypeById((int)row["TypeId"]);

                if (entry.Type == null)
                {
                    throw new CalendarEntryDataSourceException(
                        "Undefined entry type.");
                }

                calendarEntries[entry.EntryId] = entry;
            }
        }

        /// <summary>
        /// <para>Finds all calendar entries from the data source layer. If no entry exists, an empty list will
        /// be returned.</para>
        /// </summary>
        /// <returns>All calendar entries stored in the data source or an empty list if no entry exists.
        /// </returns>
        public IList<BaseCalendarEntry> FindAllEntries()
        {
            IList<BaseCalendarEntry> result = new List<BaseCalendarEntry>();
            foreach (CalendarEntry entry in calendarEntries.Values)
            {
                result.Add(entry);
            }
            return result;
        }

        /// <summary>
        /// <para>Finds the calendar entry with the given id, if not such entry exists, <c>null</c> will
        /// be returned.</para>
        /// </summary>
        /// <param name="id">The id of the desired calendar entry.</param>
        /// <returns>The calendar entry with the given id or <c>null</c> if no such entry exists.</returns>
        public BaseCalendarEntry FindEntryById(int id)
        {
            if (calendarEntries.ContainsKey(id))
            {
                return calendarEntries[id];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// <para>Finds all calendar entries with the given type, if no such records exists, an empty list will
        /// be returned.</para>
        /// </summary>
        /// <param name="type">The type by which to search calendar entries.</param>
        /// <returns>All calendar entries with the given type or an empty list if no such entry exists.
        /// </returns>
        /// <exception cref="ArgumentNullException">If <c>type</c> is <c>null</c>.</exception>
        public IList<BaseCalendarEntry> FindEntriesByType(CalendarEntryType type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type", "Argument type should not be null.");
            }

            IList<BaseCalendarEntry> result = new List<BaseCalendarEntry>();
            foreach (CalendarEntry entry in calendarEntries.Values)
            {
                if (entry.Type == type)
                {
                    result.Add(entry);
                }
            }
            return result;
        }

        /// <summary>
        /// <para>Finds all calendar entries whose description partially matches the given description. If no
        /// such entry exists, an empty list will be returned. Here partially match the given description is a
        /// substring of the description of any desired entry.</para>
        /// </summary>
        /// <param name="description">The description by which to search calendar entries.</param>
        /// <returns>All calendar entries whose description partially matches the given description or an empty
        /// list if no such entry exists.</returns>
        /// <exception cref="ArgumentNullException">If <c>description</c> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">If <c>description</c> is an empty string.</exception>
        public IList<BaseCalendarEntry> FindEntriesByDescription(string description)
        {
            if (description == null)
            {
                throw new ArgumentNullException("description", "Argument description should not be null.");
            }
            if (description.Trim().Length == 0)
            {
                throw new ArgumentException("Argument description should not be empty.", "description");
            }

            IList<BaseCalendarEntry> result = new List<BaseCalendarEntry>();
            foreach (CalendarEntry entry in calendarEntries.Values)
            {
                if (entry.Description.Contains(description))
                {
                    result.Add(entry);
                }
            }
            return result;
        }

        /// <summary>
        /// <para>Finds all calendar entries within the given date range(inclusive), if no such entry exists,
        /// an empty list will be returned.</para>
        /// </summary>
        /// <param name="startDate">The start date of the date range by which to search calendar entries.</param>
        /// <param name="endDate">The end date of the date range by which to search calendar entries.</param>
        /// <returns>All calendar entries within the given date range or an empty list if no such entry exists.
        /// </returns>
        /// <exception cref="ArgumentException">If <c>endDate</c> is before <c>startDate</c>.
        /// </exception>
        public IList<BaseCalendarEntry> FindEntriesByDateRange(DateTime startDate, DateTime endDate)
        {
            if (startDate > endDate)
            {
                throw new ArgumentException("endData should not be before startDate", "endDate");
            }

            IList<BaseCalendarEntry> result = new List<BaseCalendarEntry>();
            foreach (CalendarEntry entry in calendarEntries.Values)
            {
                if (entry.Date >= startDate && entry.Date <= endDate)
                {
                    result.Add(entry);
                }
            }
            return result;
        }

        /// <summary>
        /// <para>Finds the calendar entry type with the given id, if no such entry type exists,
        /// <c>null</c> will be returned.</para>
        /// </summary>
        /// <param name="typeId">The id of the desired calendar entry type.</param>
        /// <returns>The desired calendar entry type or <c>null</c> if no such type exists.</returns>
        public CalendarEntryType FindEntryTypeById(int typeId)
        {
            if (calendarEntryTypes.ContainsKey(typeId))
            {
                return calendarEntryTypes[typeId];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// <para>Finds all calendar entry types whose description partially matches the given description.
        /// If no such entry type exists, an empty list will be returned. Here partially match the given
        /// description is a substring of the description of any desired entry type.</para>
        /// </summary>
        /// <param name="description">The description used to search calendar entry types.</param>
        /// <returns>All calendar entry types whose description partially matches the given description or an
        /// empty list if no such entry type exists.</returns>
        /// <exception cref="ArgumentNullException">If <c>description</c> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">If <c>description</c> is an empty string.</exception>
        public IList<CalendarEntryType> FindEntryTypesByDescription(string description)
        {
            if (description == null)
            {
                throw new ArgumentNullException("description", "Argument description should not be null.");
            }
            if (description.Trim().Length == 0)
            {
                throw new ArgumentException("Argument description should not be empty.", "description");
            }

            IList<CalendarEntryType> result = new List<CalendarEntryType>();
            foreach (CalendarEntryType entryType in calendarEntryTypes.Values)
            {
                if (entryType.Description.Contains(description))
                {
                    result.Add(entryType);
                }
            }
            return result;
        }
    }
}
